<?php
/**
 * ESDBConnection class file
 *
 * @author Yoyon Cahyono <yoyoncahyono@gmail.com>
 * @link http://ondev.enggon.com/
 * @copyright Copyright &copy; 2011 Enggon.com
 * @license http://www.enggon.com/license/
 */

/**
 * ESDBConnection represents a connection to a database.
 *
 * ESDBConnection works together with {@link ESDBCommand} and {@link ESDBDataReader}
 * to provide data access to Amazon SimpleDB
 * in a common set of APIs. They are a thin wrapper of the Amazon SimpleDB
 * {@link http://docs.amazonwebservices.com/AmazonSimpleDB/latest/DeveloperGuide/MakingRESTRequests.html REST library}
 *
 * To work with Amazon SimpleDB you need to specifying {@link accessKey}, 
 * {@link secretKey} and {@link host} if necessary.
 *
 * The following example shows how to create a ESDBConnection instance:
 * <pre>
 * $connection=new ESDBConnection($accessKey,$secretKey,$host);
 * </pre>
 *
 * After the DB connection is established, one can execute a SQL query and fetch the result set like the following:
 * <pre>
 * $command=$connection->createCommand($sqlStatement);
 * $reader=$command->query();
 *
 * // each $row is an array representing a row of data
 * foreach($reader as $row) ...
 * </pre>
 *
 * Since ESDBConnection implements the interface IApplicationComponent, it can
 * be used as an application component and be configured in application configuration,
 * like the following,
 * <pre>
 * array(
 *     'components'=>array(
 *         'sdb'=>array(
 *             'class'=>'ESDBConnection',
 *             'accessKey'=>'your AWS Access Key',
 *             'secretKey'=>'your AWS Secret Key',
 *             'host'=>'default to sdb.amazonaws.com',
 *         ),
 *     ),
 * )
 * </pre>
 *
 * @author Yoyon Cahyono <yoyoncahyono@gmail.com>
 * @version $Id: ESDBConnection.php 1 2011-12-28 12:39:41Z yoyoncahyono $
 * @package ext.yii-simpledb
 */
class ESDBConnection extends CApplicationComponent {
	/**
	 * @var integer max item size on batch operation
	 * @link http://docs.amazonwebservices.com/AmazonSimpleDB/latest/DeveloperGuide/SDBLimits.html
	 */
	const MAX_ITEM_BATCH_SIZE = 25;
	/**
	 * @var string the accessKey for establishing Amazon SimpleDB connection. Defaults to empty string.
	 */
	public $accessKey='';
	/**
	 * @var string the secretKey for establishing Amazon SimpleDB connection. Defaults to empty string.
	 */
	public $secretKey='';
	/**
	 * @var string the host for establishing Amazon SimpleDB connection. Defaults to empty string.
	 */
	public $host='';
	/**
	 * @var boolean whether to use SSL connection.
	 * Defaults to true.
	 */
	public $ssl=true;
	/**
	 * @var string The name of a file holding one or more certificates to verify the peer with in SSL connection. Defaults to empty string.
	 */
	public $cainfo='';
	/**
	 * @var boolean verifyHost on SSL connection. Defaults to true.
	 */
	public $verifyHost=true;
	/**
	 * @var boolean verifyPeer on SSL connection. Defaults to true.
	 */
	public $verifyPeer=true;
	/**
	 * @var boolean whether to enable profiling the SQL statements being executed.
	 * Defaults to false. This should be mainly enabled and used during development
	 * to find out the bottleneck of SQL executions.
	 */
	public $enableProfiling=false;
	/**
	 * @var integer number of seconds that query results can remain valid in cache.
	 * Use 0 or negative value to indicate not caching query results (the default behavior).
	 *
	 * In order to enable query caching, this property must be a positive
	 * integer and {@link queryCacheID} must point to a valid cache component ID.
	 *
	 * The method {@link cache()} is provided as a convenient way of setting this property
	 * and {@link queryCachingDependency} on the fly.
	 *
	 * @see cache
	 * @see queryCachingDependency
	 * @see queryCacheID
	 */
	public $queryCachingDuration=0;
	/**
	 * @var CCacheDependency the dependency that will be used when saving query results into cache.
	 * @see queryCachingDuration
	 */
	public $queryCachingDependency;
	/**
	 * @var integer the number of SQL statements that need to be cached next.
	 * If this is 0, then even if query caching is enabled, no query will be cached.
	 * Note that each time after executing a SQL statement (whether executed on DB server or fetched from
	 * query cache), this property will be reduced by 1 until 0.
	 */
	public $queryCachingCount=0;
	/**
	 * @var string the ID of the cache application component that is used for query caching.
	 * Defaults to 'cache' which refers to the primary cache application component.
	 * Set this property to false if you want to disable query caching.
	 */
	public $queryCacheID='cache';
	/**
	 * @var string the default prefix for table names. Defaults to null, meaning no table prefix.
	 * By setting this property, any token like '{{tableName}}' in {@link ESDBCommand::text} will
	 * be replaced by 'prefixTableName', where 'prefix' refers to this property value.
	 */
	public $tablePrefix;
	
	/**
	 * @var boolean consistency data read. Defaults to false.
	 * @link http://developer.amazonwebservices.com/connect/entry.jspa?externalID=3572
	 */
	public $consistentRead=false;
	
	/**
	 * @var boolean default put attributes replace. Defaults to false.
	 */
	public $defaultReplace=false;
	
	// information related to last request
	/**
	 * @var string measure of machine utilization for the last request
	 */
	private $_boxUsage;
	/**
	 * @var string the last requestId
	 */
	private $_requestId;
	/**
	 * @var string the last request nextToken
	 */
	private $_nextToken;
	/**
	 * @var string the last request errorCode
	 */
	private $_errorCode;
	
	private $_sdb;
	
	/**
	 * Constructor.
	 * Note, the DB connection is not established when this connection
	 * instance is created.
	 * @param string $accessKey The access key for AWS SimpleDB connection.
	 * @param string $secretKey The secret key for AWS SimpleDB connection.
	 * @param string $host The host for AWS SimpleDB connection.
	 */
	public function __construct($accessKey='',$secretKey='',$host='')
	{
		$this->accessKey=$accessKey;
		$this->secretKey=$secretKey;
		if(empty($host)) {
			$host='sdb.amazonaws.com';
		}
		$this->host=$host;
	}
	/**
	 * Get the latest SimpleDB next token
	 * @return string latest SimpleDB next token from this connection
	 */
	public function getNextToken() {
		return $this->_nextToken;
	}
	/**
	 * Creates a command for execution.
	 * @param mixed $query the DB query to be executed. This can be either a string representing a SQL statement,
	 * or an array representing different fragments of a SQL statement. Please refer to {@link ESDBCommand::__construct}
	 * for more details about how to pass an array as the query. If this parameter is not given,
	 * you will have to call query builder methods of {@link ESDBCommand} to build the DB query.
	 * @return ESDBCommand the DB command
	 */
	public function createCommand($query=null)
	{
		return new ESDBCommand($this,$query);
	}
	/**
	 * Sets the parameters about query caching.
	 * This method can be used to enable or disable query caching.
	 * By setting the $duration parameter to be 0, the query caching will be disabled.
	 * Otherwise, query results of the new SQL statements executed next will be saved in cache
	 * and remain valid for the specified duration.
	 * If the same query is executed again, the result may be fetched from cache directly
	 * without actually executing the SQL statement.
	 * @param integer $duration the number of seconds that query results may remain valid in cache.
	 * If this is 0, the caching will be disabled.
	 * @param CCacheDependency $dependency the dependency that will be used when saving the query results into cache.
	 * @param integer $queryCount number of SQL queries that need to be cached after calling this method. Defaults to 1,
	 * meaning that the next SQL query will be cached.
	 * @return ESDBConnection the connection instance itself.
	 */
	public function cache($duration, $dependency=null, $queryCount=1)
	{
		$this->queryCachingDuration=$duration;
		$this->queryCachingDependency=$dependency;
		$this->queryCachingCount=$queryCount;
		return $this;
	}
	/**
	 * Quotes a string value for use in a query.
	 * @param string $str string to be quoted
	 * @return string the properly quoted string
	 * @see http://docs.amazonwebservices.com/AmazonSimpleDB/latest/DeveloperGuide/QuotingRulesSelect.html
	 */
	public function quoteValue($str)
	{
		$quotedStr = '"'.str_replace('"', '""', $str).'"';
		return $quotedStr;
	}

	/**
	 * Quotes a table name for use in a query.
	 * @param string $name table name
	 * @return string the properly quoted table name
	 * @see http://docs.amazonwebservices.com/AmazonSimpleDB/latest/DeveloperGuide/QuotingRulesSelect.html
	 */
	public function quoteTableName($name)
	{
		return '`'.$name.'`';
	}

	/**
	 * Quotes a column name for use in a query.
	 * @param string $name column name
	 * @return string the properly quoted column name
	 * @see http://docs.amazonwebservices.com/AmazonSimpleDB/latest/DeveloperGuide/QuotingRulesSelect.html
	 */
	public function quoteColumnName($name)
	{
		if (!($name === '*' || $name === 'itemName()')) {
			$name = '`'.$name.'`';
		}
		return $name;
	}
	/**
	 * Creates a SimpeDB request.
	 * @param string $action SimpleDB action
	 * @param string $method HTTP request method
	 * @param string $domain Domain name
	 * @return ESDBRequest the SimpleDB request
	 */
	public function createRequest($action, $method, $domain = null)
	{
		$this->_boxUsage = null;
		$this->_requestId = null;
		$this->_nextToken = null;
		$this->_errorCode = null;
		return new ESDBRequest($this, $action, $method, $domain);
	}
	/**
	* Create a domain
	*
	* @param string $domain The domain to create
	* @return boolean of successfull operation
	* @throws CException execution failed
	*/
	public function createDomain($domain) {
		$response = $this->createRequest('CreateDomain', 'POST', $domain)->getResponse();
		if(isset($response->body->ResponseMetadata->RequestId)) {
			$this->_requestId = (string)($response->body->ResponseMetadata->RequestId);
		}
		if(isset($response->body->ResponseMetadata->BoxUsage)) {
			$this->_boxUsage = (string)($response->body->ResponseMetadata->BoxUsage);
		}

		return true;
	}

	/**
	* Delete a domain
	*
	* @param string $domain The domain to delete
	* @return boolean
	* @throws CException execution failed
	*/
	public function deleteDomain($domain) {
		$response = $this->createRequest('DeleteDomain', 'DELETE', $domain)->getResponse();

		if(isset($response->body->ResponseMetadata->RequestId)) {
			$this->_requestId = (string)($response->body->ResponseMetadata->RequestId);
		}
		if(isset($response->body->ResponseMetadata->BoxUsage)) {
			$this->_boxUsage = (string)($response->body->ResponseMetadata->BoxUsage);
		}

		return true;
	}
	/**
	 * Get a list of domains.
	 * @return array or domains
	 * @throws CException execution failed
	 */
	public function listDomain()
	{
		$response = $this->createRequest('ListDomains', 'GET')->getResponse();

		$results = array();
		if (!isset($response->body->ListDomainsResult))
		{
			return $results;
		}

		foreach($response->body->ListDomainsResult->DomainName as $d)
		{
			$results[] = (string)$d;
		}

		if(isset($response->body->ResponseMetadata->RequestId)) {
			$this->_requestId = (string)($response->body->ResponseMetadata->RequestId);
		}
		if(isset($response->body->ResponseMetadata->BoxUsage)) {
			$this->_boxUsage = (string)($response->body->ResponseMetadata->BoxUsage);
		}
		return $results;
	}
	/**
	* Get a domain's metadata
	*
	* @param string $domain The domain
	* @return array | false
	*	Array returned
	*	(
	*		[itemCount] => 3
	*		[itemNamesSizeBytes] => 16
	*		[attributeNameCount] => 9
	*		[attributeNamesSizeBytes] => 76
	*		[attributeValueCount] => 13
	*		[attributeValuesSizeBytes] => 65
	*		[timestamp] => 1247238402
	*	)
	*/
	public function domainMetadata($domain) {
		$response = $this->createRequest('DomainMetadata', 'GET', $domain)->getResponse();
		$results = array();
		if (!isset($response->body->DomainMetadataResult)) {
			return $results;
		}
		if(isset($response->body->DomainMetadataResult->ItemCount)) {
			$results['itemCount'] = (string)($response->body->DomainMetadataResult->ItemCount);
		}
		if(isset($response->body->DomainMetadataResult->ItemNamesSizeBytes)) {
			$results['itemNamesSizeBytes'] = (string)($response->body->DomainMetadataResult->ItemNamesSizeBytes);
		}
		if(isset($response->body->DomainMetadataResult->AttributeNameCount)) {
			$results['attributeNameCount'] = (string)($response->body->DomainMetadataResult->AttributeNameCount);
		}
		if(isset($response->body->DomainMetadataResult->AttributeNamesSizeBytes)) {
			$results['attributeNamesSizeBytes'] = (string)($response->body->DomainMetadataResult->AttributeNamesSizeBytes);
		}
		if(isset($response->body->DomainMetadataResult->AttributeValueCount))	{
			$results['attributeValueCount'] = (string)($response->body->DomainMetadataResult->AttributeValueCount);
		}
		if(isset($response->body->DomainMetadataResult->AttributeValuesSizeBytes)) {
			$results['attributeValuesSizeBytes'] = (string)($response->body->DomainMetadataResult->AttributeValuesSizeBytes);
		}
		if(isset($response->body->DomainMetadataResult->Timestamp)) {
			$results['timestamp'] = (string)($response->body->DomainMetadataResult->Timestamp);
		}
		if(isset($response->body->ResponseMetadata->RequestId)) {
			$this->_requestId = (string)($response->body->ResponseMetadata->RequestId);
		}
		if(isset($response->body->ResponseMetadata->BoxUsage)) {
			$this->_boxUsage = (string)($response->body->ResponseMetadata->BoxUsage);
		}

		return $results;
	}

	/**
	* Evaluate a select expression
	*
	* Function provided by Matthew Lanham
	*
	* @param string  $select The select expression to evaluate.
	* @param string  $nexttoken The token to start from when retrieving results
	* @param boolean $consistentRead - force consistent read = true
	* @return array | false
	*/
	public function select($select, $nexttoken = null, $consistentRead = null) {
		$request = $this->createRequest('Select', 'GET');

		if($select != '') {
			$request->setParameter('SelectExpression', $select);
		}
		if($nexttoken !== null) {
			$request->setParameter('NextToken', $nexttoken);
		}
		if ($consistentRead !== false) {
			if($consistentRead === true || $this->consistentRead === true)
				$request->setParameter('ConsistentRead', 'true');
		}

		$response = $request->getResponse();
		$results = array();

		if (!isset($response->body->SelectResult)) {
			return $results;
		}

		if(isset($response->body->ResponseMetadata->RequestId)) {
			$this->_requestId = (string)($response->body->ResponseMetadata->RequestId);
		}
		if(isset($response->body->ResponseMetadata->BoxUsage)) {
			$this->_boxUsage = (string)($response->body->ResponseMetadata->BoxUsage);
		}
		if ($response->body->SelectResult->NextToken) {
			$this->_nextToken = (string)$response->body->SelectResult->NextToken;
		}

		foreach($response->body->SelectResult->Item as $i) {
			$item = array('Name' => (string)($i->Name), 'Attributes' => array());
			foreach($i->Attribute as $a) {
				if(isset($item['Attributes'][(string)($a->Name)])) {
					$temp = (array)($item['Attributes'][(string)($a->Name)]);
					$temp[] = (string)($a->Value);
					$item['Attributes'][(string)($a->Name)] = $temp;
				} else {
					$item['Attributes'][(string)($a->Name)] = (string)($a->Value);
				}
			}
			$results[] = $item;
		}
		return $results;
	}
	/**
	 * Get attributes associated with an item
	 *
	 * @param string $domain The domain containing the desired item
	 * @param string $itemName The desired item
	 * @param array $attributes array of attributes to retrieve, or all if unspecified.
	 * @param boolean $consistentRead - force consistent read = true
	 * @return boolean
	 */
	public function getAttributes($domain, $itemName, $attributes = null, $consistentRead = null) {
		$request = $this->createRequest('GetAttributes', 'GET', $domain);
	
		$request->setParameter('ItemName', $itemName);
	
		if($attributes !== null)	{
			if(is_array($attributes)) {
				$i=0;
				foreach ($attributes as $value) {
					$request->setParameter('AttributeName.'.$i, $value);
					$i++;
				}
			} else {
				$request->setParameter('AttributeName', $attributes);
			}
		}
	
		if ($consistentRead !== false) {
			if($consistentRead === true || $this->consistentRead === true)
				$request->setParameter('ConsistentRead', 'true');
		}
	
		$response = $request->getResponse();
		$results = array();
		if (!isset($response->body->GetAttributesResult))	{
			return $results;
		}
	
		foreach($response->body->GetAttributesResult->Attribute as $a) {
			if(isset($results[(string)($a->Name)]))	{
				$temp = (array)($results[(string)($a->Name)]);
				$temp[] = (string)($a->Value);
				$results[(string)($a->Name)] = $temp;
			} else {
				$results[(string)($a->Name)] = (string)($a->Value);
			}
		}
	
		if(isset($response->body->ResponseMetadata->RequestId)) {
			$this->_requestId = (string)($response->body->ResponseMetadata->RequestId);
		}
		if(isset($response->body->ResponseMetadata->BoxUsage)) {
			$this->_boxUsage = (string)($response->body->ResponseMetadata->BoxUsage);
		}
	
		return $results;
	}
	
	/**
	 * Normalize items attributes array so it have apropiate value and/or replace array element.
	 * @param array $attributes
	 * @param boolean $defaultReplace
	 * @return array attributes
	 */
	public function normalizeItems($items, $replace = null) {
		$normalized = array();
		$data=array();
		$replace = $replace?$replace:$this->defaultReplace;
		foreach ($items as $name=>$attributes) {
			if(!is_array($attributes)) {
				throw new ESDBException(__METHOD__, 'Array of attributes expected');
			} else {
				foreach ($attributes as $attribute=>$value) {
					$attr = array();
					if (is_array($value)) {
						$attr['value'] = @$value['value']?$value['value']:$value;
					} else {
						$attr['value'] = $value;
					}
					if($replace || @$value['replace'] === true) {
						$attr['replace']  = "true";
					}
					$normalized[$name][$attribute]=$attr;
				}
			}
		}
		if (empty($normalized)) {
			$normalized = $items;
		}
		return $normalized;
	}
	
	
	/**
	 * Create or update attributes on an item
	 *
	 * @param string  $domain The domain containing the desired item
	 * @param string  $itemName The desired item
	 * @param array $attributes An array of 
	 * (attribute_name => value) or array of 
	 * (attribute_name => array(value=>value,replace=>[true])) 
	 * where replace is a boolean of whether to replace the item.
	 * value can be scalar or an array like array(value1,value2,...)
	 * replace is optional, and defaults to $defaultReplace (below).
	 * If value is an array, multiple values are put.
	 * @param array $expected An array of (name => (value)), or (name => (exists = "false"))
	 * @param boolean $defaultReplace Specifies the default value to use for 'replace'
	 *							for each attribute.  Defaults to false. Setting this to true
	 *							will cause all attributes to replace any existing attributes.
	 * @return boolean
	 */
	public function putAttributes($domain, $items, $expected = null, $replace = null) {
		if(count($items) > 1) {
			$message = 'The maximum number of items in putAttributes is only one';
			$message .= ', but '.count($items).' items were provided.  Aborting the call.';
			throw new ESDBException(__METHOD__, $message);
		}
		$items = $this->normalizeItems($items,$replace);
		$request = $this->createRequest('PutAttributes', 'POST', $domain);
		$itemName = key($items);
		$attributes = current($items);
		$request->setParameter('ItemName', $itemName);
		
		$i = 0;
		foreach($attributes as $attribute => $value) {
			if (is_array($value['value'])) {
				foreach ($value['value'] as $val) {
					$request->setParameter('Attribute.'.$i.'.Name', $attribute);
					$request->setParameter('Attribute.'.$i.'.Value', $val, false);
					if(isset($value['replace'])) {
						$request->setParameter('Attribute.'.$i.'.Replace', $value['replace']);
					}
					$i++;
				}
			} else {
				$request->setParameter('Attribute.'.$i.'.Name', $attribute);
				$request->setParameter('Attribute.'.$i.'.Value', $value['value']);
				if(isset($value['replace'])) {
					$request->setParameter('Attribute.'.$i.'.Replace', $value['replace']);
				}
				$i++;
			}
		}
		if(is_array($expected))	{
			foreach($expected as $name => $v) {
				if(is_array($v['value']))	{  // expected value
					foreach($v['value'] as $val) {
						$request->setParameter('Expected.'.$i.'.Name', $name);
						$request->setParameter('Expected.'.$i.'.Value', $val);
						$i++;
					}
				}
				else if(isset($v['value'])) {
					$request->setParameter('Expected.'.$i.'.Name', $name);
					$request->setParameter('Expected.'.$i.'.Value', $v['value']);
					$i++;
				}
	
				if(is_array(@$v['exists']))	{
					foreach($v['exists'] as $val) { // expected does not exist
						$request->setParameter('Expected.'.$i.'.Name', $name);
						$request->setParameter('Expected.'.$i.'.Exists', $val);
						$i++;
					}
				}
				else if(@$v['exists']) {
					$request->setParameter('Expected.'.$i.'.Name', $name);
					$request->setParameter('Expected.'.$i.'.Exists', $v['exists']);
					$i++;
				}
			}
		}
		$response = $request->getResponse();
		if(isset($response->body->ResponseMetadata->RequestId)) {
			$this->_requestId = (string)($response->body->ResponseMetadata->RequestId);
		}
		if(isset($response->body->ResponseMetadata->BoxUsage)) {
			$this->_boxUsage = (string)($response->body->ResponseMetadata->BoxUsage);
		}
	
		return true;
	}
	/**
	 * Create or update attributes on multiple items
	 * MAX 25 items per write (SimpleDB limit)
	 *
	 * Original Function provided by Matthew Lanham
	 *
	 * @param string $domain The domain containing the desired item
	 * @param array  $items An array of items of (item_name=>attributes). attrributes are array of (attribute_name=>value)
	 *	If replace is omitted it defaults to false.
	 *	Optionally, attributes may just be a single string value, and replace will default to false.
	 * @param boolean $defaultReplace Specifies the default value to use for 'replace'
	 *							for each attribute.  Defaults to false. Setting this to true
	 *							will cause all attributes to replace any existing attributes.
	 * @return boolean
	 */
	public function batchPutAttributes($domain, $items, $replace = null) {
		if(count($items) > self::MAX_ITEM_BATCH_SIZE) {
			$message = 'The maximum number of attributes in batchPutAttributes is '.self::MAX_ITEM_BATCH_SIZE;
			$message .= ', but '.count($items).' items were provided.  Aborting the call.';
			throw new ESDBException(__METHOD__, $message);
		}
		$items = $this->normalizeItems($items);

		$request = $this->createRequest('BatchPutAttributes', 'POST', $domain);
	
		$ii = 0;
		foreach($items as $item=>$attributes)
		{
			$request->setParameter('Item.'.$ii.'.ItemName', $item);
	
			$i = 0;
			foreach($attributes as $attribute => $value) {
				if (is_array($value['value'])) {
					foreach ($value['value'] as $val) {
						$request->setParameter('Item.'.$ii.'.Attribute.'.$i.'.Name', $attribute);
						$request->setParameter('Item.'.$ii.'.Attribute.'.$i.'.Value', $val, false);
						if(isset($value['replace'])) {
							$request->setParameter('Item.'.$ii.'.Attribute.'.$i.'.Replace', $value['replace']);
						}
						$i++;
					}
				} else {
					$request->setParameter('Item.'.$ii.'.Attribute.'.$i.'.Name', $attribute);
					$request->setParameter('Item.'.$ii.'.Attribute.'.$i.'.Value', $value['value']);
					if(isset($value['replace'])) {
						$request->setParameter('Item.'.$ii.'.Attribute.'.$i.'.Replace', $value['replace']);
					}
					$i++;
				}
			}
			$ii++;
		}
		$response = $request->getResponse();
		if(isset($response->body->ResponseMetadata->RequestId)) {
			$this->_requestId = (string)($response->body->ResponseMetadata->RequestId);
		}
		if(isset($request->body->ResponseMetadata->BoxUsage)) {
			$this->_boxUsage = (string)($response->body->ResponseMetadata->BoxUsage);
		}
	
		return true;
	}

	/**
	 * Delete attributes associated with an item
	 *
	 * @param string  $domain The domain containing the desired item
	 * @param string  $item The desired item
	 * @param integer $attributes An array of (name => value)
	 *				value is either a specific value or null.
	 *				setting the value will erase the attribute with the given
	 *				name and value (for multi-valued attributes).
	 *				If array is unspecified, all attributes are deleted.
	 * @return boolean
	 */
	public function deleteAttributes($domain, $item, $attributes = null, $expected = null) {
		$request = $this->createRequest('DeleteAttributes', 'DELETE', $domain);
	
		$request->setParameter('ItemName', $item);
	
		$i = 0;
		if(isset($attributes)) {
			foreach($attributes as $name => $value) {
				$request->setParameter('Attribute.'.$i.'.Name', $name);
				if(isset($value))	{
					$request->setParameter('Attribute.'.$i.'.Value', $value);
				}
				$i++;
			}
		}
	
		if(is_array($expected))	{
			foreach($expected as $name => $v) {
				if(is_array($v['value']))	{  // expected value
					foreach($v['value'] as $val) {
						$request->setParameter('Expected.'.$i.'.Name', $name);
						$request->setParameter('Expected.'.$i.'.Value', $val);
						$i++;
					}
				}
				else if(isset($v['value'])) {
					$request->setParameter('Expected.'.$i.'.Name', $name);
					$request->setParameter('Expected.'.$i.'.Value', $v['value']);
					$i++;
				}
	
				if(is_array($v['exists']))	{
					foreach($v['exists'] as $val) { // expected does not exist
						$request->setParameter('Expected.'.$i.'.Name', $name);
						$request->setParameter('Expected.'.$i.'.Exists', $val);
						$i++;
					}
				}
				else if(isset($v['exists'])) {
					$request->setParameter('Expected.'.$i.'.Name', $name);
					$request->setParameter('Expected.'.$i.'.Exists', $v['exists']);
					$i++;
				}
			}
		}
	
		$response = $request->getResponse();
		if(isset($response->body->ResponseMetadata->RequestId)) {
			$this->_requestId = (string)($response->body->ResponseMetadata->RequestId);
		}
		if(isset($rest->body->ResponseMetadata->BoxUsage)) {
			$this->_boxUsage = (string)($response->body->ResponseMetadata->BoxUsage);
		}
	
		return true;
	}
	/**
	 * Delete attributes on multiple items
	 * MAX 25 items per write (SimpleDB limit)
	 *
	 * @param string $domain The domain containing the desired item
	 * @param array $items An array of items of (item_name=>attributes). attrributes are array of (attribute_name=>value)
	 * @return boolean
	 */
	public function batchDeleteAttributes($domain, $items) {
		if(count($items) > self::MAX_ITEM_BATCH_SIZE) {
			$message = 'The maximum number of attributes in batchDeleteAttributes is '.self::MAX_ITEM_BATCH_SIZE;
			$message .= ', but '.count($items).' items were provided.  Aborting the call.';
			throw new ESDBException(__METHOD__, $message);
		}
		$items = $this->normalizeItems($items);
		$request = $this->createRequest('BatchDeleteAttributes', 'GET', $domain);
	
		$ii = 0;
		foreach($items as $item=>$attributes)
		{
			$request->setParameter('Item.'.$ii.'.ItemName', $item);
	
			$i = 0;
			foreach($attributes as $attribute => $value) {
				if (is_array($value['value'])) {
					foreach ($value['value'] as $val) {
						$request->setParameter('Item.'.$ii.'.Attribute.'.$i.'.Name', $attribute);
						$request->setParameter('Item.'.$ii.'.Attribute.'.$i.'.Value', $val, false);
						$i++;
					}
				} else {
					$request->setParameter('Item.'.$ii.'.Attribute.'.$i.'.Name', $attribute);
					$request->setParameter('Item.'.$ii.'.Attribute.'.$i.'.Value', $value['value']);
					$i++;
				}
			}
			$ii++;
		}
		$response = $request->getResponse();
		if(isset($response->body->ResponseMetadata->RequestId)) {
			$this->_requestId = (string)($response->body->ResponseMetadata->RequestId);
		}
		if(isset($request->body->ResponseMetadata->BoxUsage)) {
			$this->_boxUsage = (string)($response->body->ResponseMetadata->BoxUsage);
		}
	
		return true;
	}
	
}